#!/bin/bash
#
# OpenSSH 升级脚本 - 升级到 OpenSSH 10.0p1
# 作者: 系统管理员
# 版本: 1.0
# 创建时间: $(date '+%Y-%m-%d %H:%M:%S')
#
# 描述: 此脚本用于升级系统中的 OpenSSH 到 10.0p1 版本
#       包括依赖库 zlib 和 OpenSSL 的升级
#

# 全局变量定义
SCRIPT_NAME="OpenSSH 升级脚本"
SCRIPT_VERSION="1.0"
LOG_FILE="/var/log/openssh_upgrade_$(date '+%Y%m%d_%H%M%S').log"
TEMP_DIR="/tmp/openssh_upgrade"
BACKUP_DIR="/root/openssh_backup_$(date '+%Y%m%d_%H%M%S')"

# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color

# 日志函数
log_info() {
    local message="$1"
    local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
    echo -e "${GREEN}[INFO]${NC} ${timestamp} - ${message}"
    echo "[INFO] ${timestamp} - ${message}" >> "${LOG_FILE}"
}

log_warn() {
    local message="$1"
    local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
    echo -e "${YELLOW}[WARN]${NC} ${timestamp} - ${message}"
    echo "[WARN] ${timestamp} - ${message}" >> "${LOG_FILE}"
}

log_error() {
    local message="$1"
    local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
    echo -e "${RED}[ERROR]${NC} ${timestamp} - ${message}"
    echo "[ERROR] ${timestamp} - ${message}" >> "${LOG_FILE}"
}

log_debug() {
    local message="$1"
    local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
    echo -e "${BLUE}[DEBUG]${NC} ${timestamp} - ${message}"
    echo "[DEBUG] ${timestamp} - ${message}" >> "${LOG_FILE}"
}

# 错误处理函数
handle_error() {
    local exit_code=$?
    local line_number=$1
    if [ $exit_code -ne 0 ]; then
        log_error "脚本在第 ${line_number} 行执行失败，退出码: ${exit_code}"
        cleanup_and_exit 1
    fi
}

# 清理函数
cleanup_and_exit() {
    local exit_code=${1:-0}
    log_info "正在清理临时文件..."

    if [ -d "${TEMP_DIR}" ]; then
        rm -rf "${TEMP_DIR}"
        log_info "已清理临时目录: ${TEMP_DIR}"
    fi

    if [ $exit_code -eq 0 ]; then
        log_info "脚本执行完成"
    else
        log_error "脚本执行失败，退出码: ${exit_code}"
    fi

    exit $exit_code
}

# 检查系统要求
check_system_requirements() {
    log_info "检查系统要求..."

    # 检查是否为 root 用户
    if [ "$EUID" -ne 0 ]; then
        log_error "此脚本需要 root 权限运行"
        exit 1
    fi

    # 检查系统版本
    if [ -f /etc/redhat-release ]; then
        local os_version=$(cat /etc/redhat-release)
        log_info "检测到系统: ${os_version}"
    else
        log_warn "无法检测到 RedHat/CentOS 系统，脚本可能无法正常工作"
    fi

    # 检查网络连接
    if ping -c 1 8.8.8.8 >/dev/null 2>&1; then
        log_info "网络连接正常"
    else
        log_error "网络连接失败，无法下载必要文件"
        exit 1
    fi
}

# 创建必要目录
create_directories() {
    log_info "创建必要目录..."

    mkdir -p "${TEMP_DIR}" || {
        log_error "无法创建临时目录: ${TEMP_DIR}"
        exit 1
    }

    mkdir -p "${BACKUP_DIR}" || {
        log_error "无法创建备份目录: ${BACKUP_DIR}"
        exit 1
    }

    log_info "临时目录: ${TEMP_DIR}"
    log_info "备份目录: ${BACKUP_DIR}"
}

# 备份现有配置
backup_existing_configs() {
    log_info "备份现有配置和文件..."

    # 备份 SSH 配置
    if [ -d "/etc/ssh" ]; then
        cp -r /etc/ssh "${BACKUP_DIR}/ssh_config" || log_warn "备份 SSH 配置失败"
        log_info "已备份 SSH 配置到: ${BACKUP_DIR}/ssh_config"
    fi

    # 备份现有的二进制文件
    local files_to_backup=(
        "/usr/sbin/sshd"
        "/usr/bin/ssh"
        "/usr/bin/ssh-keygen"
        "/etc/init.d/sshd"
        "/etc/pam.d/sshd"
    )

    for file in "${files_to_backup[@]}"; do
        if [ -f "$file" ]; then
            cp "$file" "${BACKUP_DIR}/$(basename $file).bak" || log_warn "备份 $file 失败"
            log_info "已备份: $file"
        fi
    done
}

# 安装依赖包
install_dependencies() {
    log_info "安装系统依赖包..."

    local packages=(
        "wget"
        "gcc"
        "make"
        "zlib-devel"
        "perl"
        "perl-Test-Simple"
        "perl-IPC-Cmd"
        "pam-devel"
    )

    for package in "${packages[@]}"; do
        log_info "安装 ${package}..."
        yum install -y "$package" >> "${LOG_FILE}" 2>&1
        if [ $? -eq 0 ]; then
            log_info "${package} 安装成功"
        else
            log_error "${package} 安装失败"
            return 1
        fi
    done
}

# 安装 zlib
install_zlib() {
    log_info "开始安装 zlib 1.3.1..."

    cd "${TEMP_DIR}" || {
        log_error "无法进入临时目录"
        return 1
    }

    log_info "下载 zlib 1.3.1..."
    wget https://www.zlib.net/zlib-1.3.1.tar.gz >> "${LOG_FILE}" 2>&1
    if [ $? -ne 0 ]; then
        log_error "下载 zlib 失败"
        return 1
    fi

    log_info "解压 zlib..."
    tar -zxvf zlib-1.3.1.tar.gz >> "${LOG_FILE}" 2>&1

    cd zlib-1.3.1 || {
        log_error "无法进入 zlib 目录"
        return 1
    }

    log_info "配置 zlib..."
    ./configure --prefix=/usr/local/zlib >> "${LOG_FILE}" 2>&1
    if [ $? -ne 0 ]; then
        log_error "zlib 配置失败"
        return 1
    fi

    log_info "编译 zlib..."
    make -j 2 >> "${LOG_FILE}" 2>&1
    if [ $? -ne 0 ]; then
        log_error "zlib 编译失败"
        return 1
    fi

    log_info "安装 zlib..."
    make install >> "${LOG_FILE}" 2>&1
    if [ $? -ne 0 ]; then
        log_error "zlib 安装失败"
        return 1
    fi

    log_info "配置 zlib 动态库..."
    echo '/usr/local/zlib/lib' >> /etc/ld.so.conf.d/zlib.conf
    ldconfig -v >> "${LOG_FILE}" 2>&1

    # 验证安装
    if [ -f "/usr/local/zlib/lib/pkgconfig/zlib.pc" ]; then
        log_info "zlib 安装成功"
        cat /usr/local/zlib/lib/pkgconfig/zlib.pc >> "${LOG_FILE}"
    else
        log_error "zlib 安装验证失败"
        return 1
    fi
}

# 安装 OpenSSL
install_openssl() {
    log_info "开始安装 OpenSSL 3.0.16..."

    cd "${TEMP_DIR}" || {
        log_error "无法进入临时目录"
        return 1
    }

    # 备份现有 OpenSSL
    if [ -d "/usr/local/openssl" ]; then
        log_info "备份现有 OpenSSL..."
        mv /usr/local/openssl /usr/local/openssl.bak
    fi

    if [ -f "/usr/bin/openssl" ]; then
        mv /usr/bin/openssl /usr/bin/openssl.bak
        log_info "已备份现有 openssl 二进制文件"
    fi

    log_info "下载 OpenSSL 3.0.16..."
    wget https://www.openssl.org/source/openssl-3.0.16.tar.gz >> "${LOG_FILE}" 2>&1
    if [ $? -ne 0 ]; then
        log_error "下载 OpenSSL 失败"
        return 1
    fi

    log_info "解压 OpenSSL..."
    tar -zxvf openssl-3.0.16.tar.gz >> "${LOG_FILE}" 2>&1

    cd openssl-3.0.16 || {
        log_error "无法进入 OpenSSL 目录"
        return 1
    }

    log_info "配置 OpenSSL..."
    ./config --prefix=/usr/local/openssl shared zlib -fPIC >> "${LOG_FILE}" 2>&1
    if [ $? -ne 0 ]; then
        log_error "OpenSSL 配置失败"
        return 1
    fi

    log_info "编译 OpenSSL (这可能需要几分钟)..."
    make -j 4 >> "${LOG_FILE}" 2>&1
    if [ $? -ne 0 ]; then
        log_error "OpenSSL 编译失败"
        return 1
    fi

    log_info "安装 OpenSSL..."
    make install >> "${LOG_FILE}" 2>&1
    if [ $? -ne 0 ]; then
        log_error "OpenSSL 安装失败"
        return 1
    fi

    log_info "配置 OpenSSL 动态库..."
    echo "/usr/local/openssl/lib64" > /etc/ld.so.conf.d/openssl.conf
    ldconfig -v >> "${LOG_FILE}" 2>&1

    log_info "创建 OpenSSL 软链接..."
    ln -sf /usr/local/openssl/bin/openssl /usr/bin/openssl

    # 验证安装
    local ssl_version=$(/usr/bin/openssl version 2>/dev/null)
    if [ $? -eq 0 ]; then
        log_info "OpenSSL 安装成功: ${ssl_version}"
    else
        log_error "OpenSSL 安装验证失败"
        return 1
    fi
}

# 安装 OpenSSH
install_openssh() {
    log_info "开始安装 OpenSSH 10.0p1..."

    cd "${TEMP_DIR}" || {
        log_error "无法进入临时目录"
        return 1
    }

    # 备份现有文件
    log_info "备份现有 SSH 文件..."
    [ -d "/etc/ssh" ] && mv /etc/ssh/ /etc/ssh_bak
    [ -f "/etc/init.d/sshd" ] && mv /etc/init.d/sshd /etc/init.d/sshd.bak
    [ -f "/usr/sbin/sshd" ] && mv /usr/sbin/sshd /usr/sbin/sshd.bak
    [ -f "/usr/bin/ssh" ] && mv /usr/bin/ssh /usr/bin/ssh.bak
    [ -f "/usr/bin/ssh-keygen" ] && mv /usr/bin/ssh-keygen /usr/bin/ssh-keygen.bak
    [ -f "/etc/pam.d/sshd" ] && cp /etc/pam.d/sshd /etc/pam.d/sshd.bak

    log_info "下载 OpenSSH 10.0p1..."
    wget https://cdn.openbsd.org/pub/OpenBSD/OpenSSH/portable/openssh-10.0p1.tar.gz >> "${LOG_FILE}" 2>&1
    if [ $? -ne 0 ]; then
        log_error "下载 OpenSSH 失败"
        return 1
    fi

    log_info "解压 OpenSSH..."
    tar -zxvf openssh-10.0p1.tar.gz >> "${LOG_FILE}" 2>&1

    cd openssh-10.0p1 || {
        log_error "无法进入 OpenSSH 目录"
        return 1
    }

    log_info "配置 OpenSSH..."
    ./configure --prefix=/usr/local/openssh \
                --sysconfdir=/etc/ssh \
                --with-ssl-dir=/usr/local/openssl \
                --with-zlib=/usr/local/zlib \
                --with-pam >> "${LOG_FILE}" 2>&1
    if [ $? -ne 0 ]; then
        log_error "OpenSSH 配置失败"
        return 1
    fi

    log_info "编译 OpenSSH (这可能需要几分钟)..."
    make >> "${LOG_FILE}" 2>&1
    if [ $? -ne 0 ]; then
        log_error "OpenSSH 编译失败"
        return 1
    fi

    log_info "安装 OpenSSH..."
    make install >> "${LOG_FILE}" 2>&1
    if [ $? -ne 0 ]; then
        log_error "OpenSSH 安装失败"
        return 1
    fi

    log_info "复制 OpenSSH 文件到系统目录..."
    cp contrib/redhat/sshd.init /etc/init.d/sshd || log_warn "复制 sshd.init 失败"
    cp /usr/local/openssh/sbin/sshd /usr/sbin/ || log_warn "复制 sshd 失败"
    cp /usr/local/openssh/bin/ssh /usr/bin/ || log_warn "复制 ssh 失败"
    cp /usr/local/openssh/bin/ssh-keygen /usr/bin/ || log_warn "复制 ssh-keygen 失败"

    log_info "配置 OpenSSH 服务..."
    chkconfig --add sshd >> "${LOG_FILE}" 2>&1

    # 配置 sshd_config
    log_info "配置 sshd_config..."
    echo 'PermitRootLogin yes' >> /etc/ssh/sshd_config
    echo 'PasswordAuthentication yes' >> /etc/ssh/sshd_config
    echo 'UsePAM yes' >> /etc/ssh/sshd_config

    # 恢复 PAM 配置
    log_info "恢复 PAM 配置..."
    cp /etc/pam.d/sshd.bak /etc/pam.d/sshd

    log_info "重启 SSH 服务..."
    /etc/init.d/sshd restart >> "${LOG_FILE}" 2>&1
    if [ $? -eq 0 ]; then
        log_info "SSH 服务重启成功"
    else
        log_warn "SSH 服务重启失败，请手动检查"
    fi

    # 验证安装
    local ssh_version=$(ssh -V 2>&1)
    if [[ $ssh_version == *"OpenSSH_10.0p1"* ]]; then
        log_info "OpenSSH 安装成功: ${ssh_version}"
    else
        log_error "OpenSSH 安装验证失败: ${ssh_version}"
        return 1
    fi
}

# 主函数
main() {
    # 设置错误处理
    set -e
    trap 'handle_error $LINENO' ERR

    log_info "==================== ${SCRIPT_NAME} v${SCRIPT_VERSION} ===================="
    log_info "开始执行 OpenSSH 升级流程"
    log_info "日志文件: ${LOG_FILE}"

    # 执行主要步骤
    check_system_requirements
    create_directories
    backup_existing_configs
    install_dependencies || cleanup_and_exit 1
    install_zlib || cleanup_and_exit 1
    install_openssl || cleanup_and_exit 1
    install_openssh || cleanup_and_exit 1

    log_info "==================== 升级完成 ===================="
    log_info "备份目录: ${BACKUP_DIR}"
    log_info "日志文件: ${LOG_FILE}"
    log_info "如果遇到问题，可以从备份目录恢复原有配置"

    cleanup_and_exit 0
}

# 脚本入口点
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
    main "$@"
fi
